/****************************************
 * Copyright (c) 2015 NiuWa.
 * All rights reserved.
 * Created on 2015年4月15日
 * 
 * Contributors:
 * 	   Percy.Lo - initial implementation
 ****************************************/
package com.niuwa.p2p.server.utils.plugins;

import java.lang.reflect.Method;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;

import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.statement.BaseStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Invocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.niuwa.p2p.exception.InvalidException;
import com.niuwa.p2p.exception.StaleObjectStateException;
import com.niuwa.p2p.server.utils.ReflectUtil;

/**
 * @title 更新拦截器，版本号控制（模拟hibernate的version实现机制）
 * 
 *        <pre>
 *  条件描述：
 * 	   1.mybatis的命令id符合规则.*byVerion；（暂不支持联表更新）
 *     2.需要使用的表加上bigInt的version字段，默认值0；
 *     3.spring加载mybatis-config.xml；
 *  结果：
 *     成功->返回更新行数
 *     失败->抛出异常StaleObjectStateException
 *     
 *  注意事项：在代码里捕获StaleObjectStateException时：
 *  		无效的捕获方式: try{
 *                         //dosomeing
 *                      }catch(StaleObjectStateException e){//这里无效捕获不到，因为被mybatis二次包装了。
 *                         //dosomeing
 *          		    }catch(Exception e){
 *          			   //dosomeing
 *          		    } 
 *          
 *            推荐方式：     try{
 *                          //dosomeing
 *                      }catch(Exception e){
 *                    	  if(ExceptionUtils.isStale(e)){
 *                    		//dosomeing
 *                        }else{
 *                       	//dosomeing
 *                        }           			 
 *          		   } 
 * </pre>
 * @author Percy.Lo
 * @version 1.0
 * @created 2015年4月16日
 */
public class UpdatePlugin implements IPlugin {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	private final static String VERSION = "version";// 版本控制关键字，与数据库字段和bean属性对应

	@Override
	public void init(String propertyParameter) throws Exception {

	}
	
	@Override
	public Object intercept(Invocation ivk) throws Exception {
		Object target = ivk.getTarget();
		if (!(target instanceof RoutingStatementHandler)) {
			return ivk.proceed();
		}
		RoutingStatementHandler statementHandler = (RoutingStatementHandler) target;
		BaseStatementHandler delegate = (BaseStatementHandler) ReflectUtil.getValueByFieldName(statementHandler, "delegate");
		MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getValueByFieldName(delegate, "mappedStatement");
		String sqlId=mappedStatement.getId();
		BoundSql boundSql = delegate.getBoundSql();
		Object parameter = boundSql.getParameterObject();
		String sql = boundSql.getSql();
		Long ver = (Long) getVersion(sqlId, parameter);
		if(ver==null)
			throw new ExecutorException("缺少version参数或数据库字段初始值为NULL，sqlId："+sqlId);
		
		if("prepare".equals(ivk.getMethod().getName())){
			String newSql = generateSql(sql, ver);
			logger.debug("plugin [UpdatePlugin] --> newSql: " + newSql);
			ReflectUtil.setValueByFieldName(boundSql, "sql", newSql);
			return ivk.proceed();
		}else if("update".equals(ivk.getMethod().getName())){
			Object o = ivk.proceed();
			Statement stmt = (Statement) ivk.getArgs()[0];
			int rs=stmt.getUpdateCount();
			if (rs == 0) {
				String msg = "Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): " + sql + "  [" + VERSION + ":" + ver + "]";
				logger.warn("StaleObjectStateException: " + msg);
				throw new StaleObjectStateException(msg);
			}
			return o;
		}
		return ivk.proceed();
	}

	@SuppressWarnings("unchecked")
	private Object getVersion(String sqlId, Object parameterObject) throws SQLException {
		Object version = null;
		if (parameterObject == null)
			throw new ExecutorException("There was null for parameter " + VERSION + " of statement " + sqlId);
		if (parameterObject instanceof Map) {
			Map<String, Object> map = (Map<String, Object>) parameterObject;
			version = map.get(VERSION);
		} else {
			try {
				Class<?> clazz = parameterObject.getClass();
				String methodName = "get" + VERSION.substring(0, 1).toUpperCase() + VERSION.substring(1);
				Method method = clazz.getMethod(methodName);
				version = method.invoke(parameterObject);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		if (version != null)
			return version;
		throw new ExecutorException("There was null for parameter " + VERSION + " of statement " + sqlId);
	}

	private String generateSql(String sql, Long ver) {
		sql = sql.replaceAll("[\\s]+", " ");
		String idxSQL=sql.toLowerCase();
		if(idxSQL.matches(".*\\bversion\\b.*")){
			throw new InvalidException("无效的SQL语句，Id为*ByVersion的sql语句中不能出现version字段！"+sql);
		}
		int idx = idxSQL.indexOf(" where ");
		if (idx != -1) {
			int idx2 = sql.indexOf(";");
			StringBuilder sb = new StringBuilder();
			sb.append(sql.substring(0, idx));
			sb.append(",").append(VERSION).append("= ").append(VERSION).append("+1");
			if (idx2 == -1) {
				sb.append(sql.substring(idx));
			} else {
				sb.append(sql.substring(idx, idx2));
			}
			sb.append(" and ").append(VERSION).append("="+ver);
			return sb.toString();
		}
		return null;
	}
}
